package com.onpositive.realmview.basic;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.ObjectOutputStream;
import java.util.HashMap;
import java.util.Map;

import com.onpositive.realmview.model.ICommitableWorkingCopy;
import com.onpositive.realmview.model.IWorkingCopy;
import com.onpositive.realmview.model.IWorkingCopyOwner;
import com.onpositive.realmview.util.PersistanceUtil;
import com.onpositive.semantic.model.api.command.ICommand;
import com.onpositive.semantic.model.api.command.ICommandExecutor;
import com.onpositive.semantic.model.api.command.ICommandListener;
import com.onpositive.semantic.model.api.command.SimpleOneArgCommand;
import com.onpositive.semantic.model.api.id.IdAccess;
import com.onpositive.semantic.model.api.property.IHasPropertyProvider;
import com.onpositive.semantic.model.api.property.IProperty;
import com.onpositive.semantic.model.api.property.IPropertyProvider;
import com.onpositive.semantic.model.api.property.PropertyAccess;

public class BasicWorkingCopy implements ICommitableWorkingCopy, IHasPropertyProvider {
	
	class DirtyStateListener implements ICommandListener {

		@Override
		public void commandExecuted(ICommand cm) {
			if (cm instanceof SimpleOneArgCommand && SimpleOneArgCommand.SET_VALUE.equals(((SimpleOneArgCommand) cm).getKind()))
				dirty = true;
		}
		
	}
	
	protected final Map<String, Object> propertyValues = new HashMap<String, Object>();
	protected Object object;
	protected IPropertyProvider originalPropertyProvider;
	protected IPropertyProvider proxyPropertyProvider;
	protected boolean dirty = false;
	
	public BasicWorkingCopy(Object object, IPropertyProvider propertyProvider) {
		this.object = object;
		this.originalPropertyProvider = propertyProvider;
		Iterable<IProperty> properties = propertyProvider.getProperties(object);
		for (IProperty property : properties) {
			propertyValues.put(property.getId(), property.getValue(object));
		}
		proxyPropertyProvider = new ProxyMapPropertyProvider(propertyProvider,object,propertyValues);
		ICommandExecutor commandExecutor = proxyPropertyProvider.getCommandExecutor();
		if (commandExecutor instanceof IListenableCommandExecutor) {
			((IListenableCommandExecutor) commandExecutor).addCommandListener(new DirtyStateListener());
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public <T> T getAdapter(Class<T> adapter) {
		if (IHasPropertyProvider.class.equals(adapter))
			return (T) this;
		if (IPropertyProvider.class.equals(adapter))
			return (T) proxyPropertyProvider;
		return null;
	}

	@Override
	public Object getOriginal() {
		return object;
	}

	@Override
	public IWorkingCopyOwner getOwner() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void setFrom(IWorkingCopy copy) {
		BasicWorkingCopy workingCopy = (BasicWorkingCopy) copy;
		propertyValues.clear();
		propertyValues.putAll(workingCopy.propertyValues);
	}

	@Override
	public boolean isDirty() {
		return dirty;
	}
	
	@Override
	public IWorkingCopy clone() {
		BasicWorkingCopy workingCopy = new BasicWorkingCopy(object,originalPropertyProvider);
		workingCopy.setFrom(this);
		return workingCopy;
	}

	@Override
	public void read(DataInputStream inputStream) {
		try {
			PersistanceUtil.readObjectFromBytes(inputStream);
			//TODO getting object by it's id
			PersistanceUtil.readMap(inputStream,(Map<String, Object>) propertyValues);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} 
	}

	@Override
	public void store(DataOutputStream outputStream) {
		ObjectOutputStream stream = null;
		try {
			Object id = IdAccess.getId(object);
			if (id == null)
				throw new NotSerializableException(object + "does not provide serializable id");
			PersistanceUtil.writeObjectBytes(id, outputStream);
			PersistanceUtil.writeMap(outputStream, propertyValues);
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (stream != null)
					stream.close();
				outputStream.close();
			} catch (IOException e1) {
			}
			
		}
	}

	@Override
	public IPropertyProvider getPropertyProvider() {
		return proxyPropertyProvider;
	}

	@Override
	public boolean commit() {
		Iterable<IProperty> properties = originalPropertyProvider.getProperties(object);
		for (IProperty property : properties) {
			PropertyAccess.setValue(property,object,getFieldValue(property.getId()));
		}
		dirty = false;
		return true;
	}

	@Override
	public Object getFieldValue(String name) {
		return propertyValues.get(name);
	}

	@Override
	public void setFieldValue(String name, Object object) {
		propertyValues.put(name,object);
		dirty = true;
	}

}
